/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include "cpputils/CppUtils.hpp"
#include "cpputils/ept/Timer.hpp"

namespace cpputils {
	namespace ept {
		using namespace utils;

		Timer::Timer(const std::string& sTimerName, bool bStartImmediate ): m_poTimerThread(NULL), m_bStopFlag(true), m_sTimerName(sTimerName) {
			m_nSystemFreq = OSTools::gethrfreq();
			if(bStartImmediate)
				start();
		}

		Timer::~Timer() {
			stop();
		}

		void Timer::schedule(TimerId nTimerId, unsigned int nDelay, Handler const & pfHandler) {
			TimerItem oItem( nTimerId, pfHandler  );
			schedule(nTimerId, nDelay, oItem);
		}

		void Timer::schedule(TimerId nTimerId, unsigned int nDelay, HandlerWithRefObject const & pfHandler, RefObject* poRefObject) {
			TimerItem oItem( nTimerId, pfHandler, poRefObject  );
			schedule(nTimerId, nDelay, oItem);
		}

		void Timer::schedule(TimerId nTimerId, unsigned int nDelay, TimerItem& oItem) {
			boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );
			if(m_bStopFlag)
				start();

			// If timer is exists
			if ( m_mTimerIds.find ( nTimerId ) != m_mTimerIds.end() )
				cancel(nTimerId); // cancel exist timer

			// Timer key is high-resolution time for call handler
			hrtime_t nKey = OSTools::gethrtime() + nDelay * m_nSystemFreq ;

			std::pair < hrtime_t, TimerItem> oTimerPair (nKey, oItem);
			Timers::iterator oInsertedTimerPair = m_mTimers.insert (m_mTimers.lower_bound(nKey), oTimerPair );

			std::pair < TimerId, Timers::iterator > oTimerIdPair ( nTimerId, oInsertedTimerPair );
			m_mTimerIds.insert ( oTimerIdPair );
		}


		void Timer::cancel(TimerId nTimerId) {
			boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );
			TimerIds::iterator itTimerIds = m_mTimerIds.find ( nTimerId );
			if( itTimerIds != m_mTimerIds.end()) {
				m_mTimers.erase ( itTimerIds->second );
				m_mTimerIds.erase ( itTimerIds );
			}
		}

		void Timer::cancelAll() {
			boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );
			m_mTimers.clear();
			m_mTimerIds.clear();
		}

		void Timer::start() {
			boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );
			if(!m_bStopFlag)
				stop();
			m_poTimerThread = new boost::thread (
				boost::bind ( &Timer::run, this )
			);
			m_bStopFlag = false;
		}

		void Timer::stop() {
			boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );
			cancelAll();
			m_bStopFlag = true;
			boost::thread* poTimerThread = m_poTimerThread;
			if(poTimerThread) {
				m_poTimerThread = NULL;
				oLock.unlock();
				poTimerThread->join();
				delete poTimerThread;
			}
		}

		void Timer::run() {
			do {
				boost::recursive_mutex::scoped_lock oLock ( m_mxTimers );

				Timers::iterator itTimer = m_mTimers.begin();
				hrtime_t nCurrentTime = OSTools::gethrtime();
				hrtime_t nFirstItemTime = 0;
				while ( itTimer != m_mTimers.end() ) {
					nFirstItemTime = itTimer->first;
					if ( nFirstItemTime <= nCurrentTime ) {
						// Removing executed timer id
						m_mTimerIds.erase( itTimer->second.getTimerId() );
						if(itTimer->second.getHandlerWithRefObject() == NULL)
							itTimer->second.executeHandler();
						else
							itTimer->second.executeHandlerWithRefObject();
						// Remove timer
						m_mTimers.erase ( itTimer );

						itTimer = m_mTimers.begin();
					}
					else
						break;
				}
				oLock.unlock();

				if(m_mTimers.empty()) {
					xtSleepMsec(AWAIT_TIMER_TIMEOUT);
				}
				else {
					hrtime_t nAwaitFirstItemTime = nFirstItemTime - nCurrentTime;
					if ( nAwaitFirstItemTime > 0 && nAwaitFirstItemTime < m_nSystemFreq * AWAIT_TIMER_TIMEOUT ) {
						xtSleepMsec ( (unsigned long) (nAwaitFirstItemTime / m_nSystemFreq) );
					}
					else
						xtSleepMsec(AWAIT_TIMER_TIMEOUT);
				}

			}
			while(!m_bStopFlag);
		}

		const std::string& Timer::getTimerName() {
			return m_sTimerName;
		}
	}
}
